package lib.comirva.audio;

import java.io.IOException;

import lib.comirva.audio.Gaussian.GaussianMixture;



/**
 * <b>Timbre Distribution</b>
 *
 * <p>Description: </p>
 * Represents a similarity measure based on the song's timbre. The distribution
 * of the MFCCs of a song is used to summerize the song. Songs having similliar
 * distributions are considert simillar.
 *
 * @see comirva.audio.extraction.TimbreDistributionExtractor
 * @author Klaus Seyerlehner
 * @version 1.0
 */
public class TimbreDistribution extends AudioFeature
{
  private final int DSR = 2000;                               //distance sample rate
  private StrongLimitedReference<PointList> sampleListRef;    //soft reference to samples drawn from the gmm
  private GaussianMixture gmm;                                //the gmm summarizing this song
  private double logLikelihood = Double.NaN;                  //log likelihood of the samples drawn from the gmm


  /**
   * Constructs a <code>TimbreDistribution</code> feature.
   *
   * @param gmm GaussianMixture the gmm summarizing the song
   */
  public TimbreDistribution(GaussianMixture gmm)
  {
    super();
    this.gmm = gmm;
  }


  /**
   * Used for xml serialization only.
   */
  protected TimbreDistribution()
  {
    super();
  }


  /**
   * Returns the log likelihood for a given sample set.
   *
   * @param samplePoints PointList sample set
   * @return double log likelihood
   */
  public double getLogLikelihood(PointList samplePoints)
  {
    return gmm.getLogLikelihood(samplePoints);
  }


  /**
   * Returns the log likelihood for the own samples points, which have been
   * drawn from the gmm representing the song.
   *
   * @return double the log likelihood of the own samples
   */
  public double getLogLikelihood()
  {
    if(Double.isNaN(logLikelihood))
      logLikelihood = getLogLikelihood(getSamplePoints());

    return logLikelihood;
  }


  /**
   * Returns a list of samples drawn from the gmm representing this song.
   *
   * @return PointList list of samples
   */
  public PointList getSamplePoints()
  {
    PointList sampleList = null;

    //if the softref exists get it => hardref
    if(sampleListRef != null)
      sampleList = sampleListRef.get();

    //create the sample points on demand
    if(sampleListRef == null || sampleList == null)
    {
      sampleList = new PointList(gmm.getDimension() , DSR+1);

      //sample form this distribution
      for (int i = 0; i < DSR; i++)
        sampleList.add(gmm.nextSample());

      //create soft reference
      sampleListRef = new StrongLimitedReference<PointList>(sampleList);

      //compute the own log likelihood
      logLikelihood = getLogLikelihood(sampleList);

      return sampleList;
    }
    else
    {
      return sampleList;
    }
  }


  /**
   * Computes the distance between two timbre distributions representing two
   * songs. This is done by mutally computing the log likelihood of drawn
   * samples.
   *
   * @param f AudioFeature another timbre distribution feature
   * @return double the distance between the two timbre distributions
   *
   * @throws ClassCastException thrown if the passed <code>AudioFeature<code> is
   *                                   not an object of type
   *                                   <code>TimbreDistribution</code>
   */
  public double getDistance(AudioFeature f) throws ClassCastException
  {
    TimbreDistribution other;
    double distance;
    PointList mySamples, otherSamples;

    //check and cast the parameter
    if(f.getType() != this.getType())
      throw new ClassCastException("features of different type are not compareable");
    else
      other = (TimbreDistribution) f;

    //get sample points
    mySamples = this.getSamplePoints();
    otherSamples = other.getSamplePoints();

    //compute the distance
    distance = this.getLogLikelihood() +
              other.getLogLikelihood() -
              other.getLogLikelihood(mySamples) -
              this.getLogLikelihood(otherSamples);

    return distance;
  }


  /**
   * For testing purpose only.
   *
   * @return GaussianMixture the gmm
   */
  public GaussianMixture getGaussianMixtureModel()
  {
    return gmm;
  }


  /**
   * Writes the xml representation of this object to the xml ouput stream.<br>
   * <br>
   * There is the convetion, that each call to a <code>writeXML()</code> method
   * results in one xml element in the output stream.
   *
   * @param writer XMLStreamWriter the xml output stream
   *
   * @throws IOException raised, if there are any io troubles
   * @throws XMLStreamException raised, if there are any parsing errors
   */
/*  public void writeXML(XMLStreamWriter writer) throws IOException, XMLStreamException
  {
    writer.writeStartElement("feature");
    writer.writeAttribute("type", getClassName());
    gmm.writeXML(writer);
    writer.writeEndElement();
  }

*/
  /**
   * Reads the xml representation of an object form the xml input stream.<br>
   * <br>
   * There is the convention, that <code>readXML()</code> starts parsing by
   * checking the start tag of this object and finishes parsing by checking the
   * end tag. The caller has to ensure, that at method entry the current token
   * is the start tag. After the method call it's the callers responsibility to
   * move from the end tag to the next token.
   *
   * @param parser XMLStreamReader the xml input stream
   *
   * @throws IOException raised, if there are any io troubles
   * @throws XMLStreamException raised, if there are any parsing errors
   */
 /* public void readXML(XMLStreamReader parser) throws IOException, XMLStreamException
  {
    parser.require(XMLStreamReader.START_ELEMENT, null, "feature");

    parser.nextTag();
    gmm = GaussianMixture.readGMM(parser);
  }
*/}
